<template>
  <div v-loading="loading">
    <div class="sw-crop__wrap">
      <div class="sw-crop__croparea">
        <div class="sw-crop__title">截取展示区域</div>
        <div class="sw-crop__cropbox">
          <div
            class="sw-crop__imgbox"
            :style="cropImgboxStyle"
            @mousemove="handleSelectMousemove"
          >
            <img :src="imgBase64" v-if="imgBase64" draggable="false" />
            <div
              class="sw-crop__select"
              @mousedown="handleSelectMousedown"
              @mouseup="handleSelectMouseup"
            >
              <div class="sw-crop__select__area" :style="cropSelectAreaStyle">
                <img
                  :src="imgBase64"
                  v-if="imgBase64"
                  :style="cropSelectAreaImgStyle"
                  draggable="false"
                />
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="sw-crop__preview">
        <div class="sw-crop__title">最终效果展示</div>
        <div class="sw-crop__previewbox">
          <div
            class="sw-crop__imgbox"
            :style="{
              height: previewBoxHeight + 'px',
              width: previewBoxWidth + 'px',
            }"
          >
            <img
              :src="imgBase64"
              v-if="imgBase64"
              :style="previewBoxImgStyle"
            />
          </div>
        </div>
      </div>
    </div>
    <div class="button-bar">
      <el-button round @click="emits('on-cancel')" :loading="loading"
        >取消</el-button
      >
      <el-button type="primary" round @click="handleCrop" :loading="loading"
        >确认</el-button
      >
    </div>
  </div>
</template>
<script setup lang="ts">
import { ElButton } from 'element-plus'
import { divide, minus, times } from './utils/count'
import { base64ToFile } from './utils/util'
import { ref, reactive, onMounted, computed } from 'vue'
const props = withDefaults(
  defineProps<{
    cropImageFile?: File | null //待裁剪的图片文件
    specsWidthPx: number //裁剪宽度
    specsHeightPx: number //裁剪高度
  }>(),
  {
    cropImageFile: null,
    specsWidthPx: 600,
    specsHeightPx: 336,
  }
)
const emits = defineEmits<{
  (e: 'on-crop', value: File | false, done: Function, loading: Function): void
  (e: 'on-cancel'): void
  (e: 'on-error', msg?: any): void
}>()

/** 选区移动方向 */
enum MOVE_DIRECTION {
  /** 默认 */
  DEFAULT = '',
  /** 水平移动 */
  HORIZONTAL = 'x',
  /** 垂直移动 */
  VERTICAL = 'y',
}

const fileType = ref('') //图片文件类型
const fileName = ref('') //图片文件名称

const selectAreaWidth = ref(0) //当前截取选区宽
const selectAreaHeight = ref(0) //当前截取选区高度
const selectAreaTop = ref(0) //当前截取选区顶部距离
const selectAreaLeft = ref(0) //当前截取选区左侧距离
const selectAreaBorderWidth = ref(1) //截取选区边框宽度
const selectMoveDirection = ref<MOVE_DIRECTION>(MOVE_DIRECTION.DEFAULT) //水平，垂直，默认
const cropSelectAreaWidth = 480 //容器宽度
const cropSelectAreaHeight = 250 //容器高度
const cropSelectAspectRatio = divide(cropSelectAreaWidth, cropSelectAreaHeight) //容器宽高比
const imgWidth = ref(0) //当前图片原宽度
const imgHeight = ref(0) //当前图片原高度
const imgBase64 = ref('') //当前图的base64地址
const imgAspectRatio = ref(0) //当前图片宽高比
const imgScaleWidth = ref(0) //缩放后图片在容器中的宽度
const imgScaleHeight = ref(0) //缩放后图片在容器中的高度
const previewBoxWidth = 238 //预览容器宽度(固定)
const previewBoxHeight = computed(() => {
  if (props.specsWidthPx <= 0) return 0
  const _scaleTimes = divide(previewBoxWidth, props.specsWidthPx)
  return times(props.specsHeightPx, _scaleTimes)
}) //133.28 //预览容器高度 根据裁剪宽度与238比例缩放
const previewBoxAspectRatio = computed(() =>
  divide(previewBoxWidth, previewBoxHeight.value)
) //预览容器宽高
const previewBoxImgHeight = ref(0) //预览图片高度
const previewBoxImgWidth = ref(0) //预览图片高度
const displacementRatio = computed(() => {
  if (selectAreaWidth.value > 0)
    return divide(previewBoxWidth, selectAreaWidth.value)
  else return 1
}) //预览图片与裁剪区域的位移比
const previewBoxImgTop = computed(() =>
  times(selectAreaTop.value, displacementRatio.value)
) //预览图片上边距
const previewBoxImgLeft = computed(() =>
  times(selectAreaLeft.value, displacementRatio.value)
) //预览图片左边距

const moveable = ref(false) //是否可拖动
const downClientX = ref(0) //鼠标按下时相对容器的x坐标
const downClientY = ref(0) //鼠标按下时相对容器的Y坐标
const lastLeft = ref(0) //上一次释放鼠标的左侧距离
const lastTop = ref(0) //上一次释放鼠标的上侧距离
const moveableMaxLeft = ref(0) //水平可移动最大距离
const moveableMaxTop = ref(0) //垂直可移动最大距离

let imageElement: HTMLImageElement

const loading = ref(false)

// 初始化裁剪容器
const initCrop = () => {
  if (imgAspectRatio.value >= cropSelectAspectRatio) {
    // console.log('判定为宽度固定，高度缩放')
    imgScaleWidth.value = 480
    const _scaleTimes = divide(imgWidth.value, 480) //获取缩小倍数，小于1放大，大于1缩小
    imgScaleHeight.value = divide(imgHeight.value, _scaleTimes)
  } else {
    // console.log('判断为高度固定，宽度缩放')
    imgScaleHeight.value = 250
    const _scaleTimes = divide(imgHeight.value, 250) //获取缩小倍数，小于1放大，大于1缩小
    imgScaleWidth.value = divide(imgWidth.value, _scaleTimes)
  }
  // 获取缩放后图片的宽高比
  let scaleAspectRatio = divide(imgScaleWidth.value, imgScaleHeight.value)
  // 根据当前图片缩放后的宽高设置选区宽高
  let specsAspectRatio = divide(props.specsWidthPx, props.specsHeightPx)
  // 同理
  if (specsAspectRatio >= scaleAspectRatio) {
    // console.log('截取区域宽度固定，高度缩放')
    selectMoveDirection.value = MOVE_DIRECTION.VERTICAL
    selectAreaWidth.value = imgScaleWidth.value
    const _scaleTimes = divide(props.specsWidthPx, selectAreaWidth.value)
    selectAreaHeight.value = divide(props.specsHeightPx, _scaleTimes)
    // 设置可移动范围
    moveableMaxTop.value = minus(imgScaleHeight.value, selectAreaHeight.value)
  } else {
    // console.log('截取区域高度固定，宽度缩放')
    selectMoveDirection.value = MOVE_DIRECTION.HORIZONTAL
    selectAreaHeight.value = imgScaleHeight.value
    const _scaleTimes = divide(props.specsHeightPx, selectAreaHeight.value)
    selectAreaWidth.value = divide(props.specsWidthPx, _scaleTimes)
    // 设置可移动范围
    moveableMaxLeft.value = minus(imgScaleWidth.value, selectAreaWidth.value)
  }
  // 获取截取预览区域图片的宽高
  if (imgAspectRatio.value >= previewBoxAspectRatio.value) {
    // console.log('预览截取图片高度固定，宽度缩放')
    previewBoxImgHeight.value = previewBoxHeight.value
    const _scaleTimes = divide(imgHeight.value, previewBoxImgHeight.value)
    previewBoxImgWidth.value = divide(imgWidth.value, _scaleTimes)
  } else {
    // console.log('预览截取图片宽度固定，高度缩放')
    previewBoxImgWidth.value = previewBoxWidth
    const _scaleTimes = divide(imgWidth.value, previewBoxImgWidth.value)
    previewBoxImgHeight.value = divide(imgHeight.value, _scaleTimes)
  }
}

// 截取区域图片尺寸样式
const cropImgboxStyle = computed(() => {
  return {
    width: imgScaleWidth.value + 'px',
    height: imgScaleHeight.value + 'px',
  }
})
// 截取区域选区尺寸样式
const cropSelectAreaStyle = computed(() => {
  return {
    width: selectAreaWidth.value + 'px',
    height: selectAreaHeight.value + 'px',
    borderWidth: selectAreaBorderWidth.value + 'px',
    top: selectAreaTop.value + 'px',
    left: selectAreaLeft.value + 'px',
  }
})
// 截取区域图片样式
const cropSelectAreaImgStyle = computed(() => {
  return {
    width: imgScaleWidth.value + 'px',
    height: imgScaleHeight.value + 'px',
    top: -(selectAreaTop.value + selectAreaBorderWidth.value) + 'px',
    left: -(selectAreaLeft.value + selectAreaBorderWidth.value) + 'px',
  }
})
// 预览区图片样式
const previewBoxImgStyle = computed(() => {
  return {
    width: previewBoxImgWidth.value + 'px',
    height: previewBoxImgHeight.value + 'px',
    top: -previewBoxImgTop.value + 'px',
    left: -previewBoxImgLeft.value + 'px',
  }
})

// 选区鼠标按下
const handleSelectMousedown = (e: MouseEvent) => {
  let { clientX, clientY } = e //获取点击位置相对容器的水平与垂直坐标
  downClientX.value = clientX //继承上次移动到的位置
  downClientY.value = clientY

  moveable.value = true
}
// 选区容器鼠标移动
const handleSelectMousemove = (e: MouseEvent) => {
  if (!moveable.value || loading.value) return
  let { clientX, clientY } = e //获取当前鼠标移动到容器的水平与垂直坐标
  //水平移动
  if (selectMoveDirection.value == MOVE_DIRECTION.HORIZONTAL) {
    let _selectAreaLeft = clientX - downClientX.value + lastLeft.value
    if (_selectAreaLeft < 0) selectAreaLeft.value = 0
    else selectAreaLeft.value = Math.min(_selectAreaLeft, moveableMaxLeft.value)
    // console.log('水平左边距', selectAreaLeft.value)
  }
  //垂直移动
  else if (selectMoveDirection.value == MOVE_DIRECTION.VERTICAL) {
    let _selectAreaTop = clientY - downClientY.value + lastTop.value
    if (_selectAreaTop < 0) selectAreaTop.value = 0
    else selectAreaTop.value = Math.min(_selectAreaTop, moveableMaxTop.value)
    // console.log('垂直上边距', selectAreaTop.value)
  }
}
// 选区鼠标抬起
const handleSelectMouseup = () => {
  if (!moveable.value) return
  moveable.value = false
  lastLeft.value = selectAreaLeft.value //记录最新的位置
  lastTop.value = selectAreaTop.value
}

/** 确认裁剪 */
const handleCrop = () => {
  loading.value = true
  // 计算裁剪区域在实际图片的实际信息
  // const dpr = window.devicePixelRatio //系统实际像素比
  const dpr = 1 //系统实际像素比
  const _scaleTimes = divide(imgWidth.value, imgScaleWidth.value) //实际缩放
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')
  canvas.width = times(props.specsWidthPx, dpr)
  canvas.height = times(props.specsHeightPx, dpr)
  const _sx = times(selectAreaLeft.value, _scaleTimes) //实际图片元素左上角开始x
  const _sy = times(selectAreaTop.value, _scaleTimes) //实际图片元素左上角开始y
  const _sWidth = times(selectAreaWidth.value, _scaleTimes) //实际图片元素距离x的距离
  const _sHeight = times(selectAreaHeight.value, _scaleTimes) //实际图片元素距离y的距离
  const _dWidth = times(props.specsWidthPx, dpr)
  const _dHeight = times(props.specsHeightPx, dpr)
  ctx?.drawImage(
    imageElement,
    _sx,
    _sy,
    _sWidth,
    _sHeight,
    0,
    0,
    _dWidth,
    _dHeight
  )
  const dataUrl = canvas.toDataURL(fileType.value || 'image/png')
  let file = base64ToFile(dataUrl, fileName.value, fileType.value)
  emits(
    'on-crop',
    file,
    () => {
      emits('on-cancel')
    },
    () => {
      loading.value = false
    }
  )
}

// 图片转base64，并获取尺寸
onMounted(() => {
  loading.value = true
  if (props.specsHeightPx == 0 || props.specsWidthPx == 0) {
    emits('on-error', '无法使用截取功能')
    return
  }
  if (props.cropImageFile == null) {
    emits('on-error', '缺少图片文件')
    return
  }
  fileType.value = props.cropImageFile.type || ''
  fileName.value = props.cropImageFile.name || ''
  const reader = new FileReader()
  reader.readAsDataURL(props.cropImageFile)
  // 获取base64，并获取图片宽度与高度
  reader.onload = (event) => {
    let res = event.target?.result
    if (typeof res == 'string') {
      try {
        imgBase64.value = res //获取图片base64地址
        const _img = new Image()
        _img.src = imgBase64.value
        _img.onload = () => {
          // 获取图片长度与宽度
          imgWidth.value = _img.width
          imgHeight.value = _img.height
          if (imgWidth.value == 0 || imgHeight.value == 0) {
            emits('on-error', '图片长或宽存在为0，截图初始化失败')
            return
          } else if (
            imgWidth.value < props.specsWidthPx ||
            imgHeight.value < props.specsHeightPx
          ) {
            emits('on-error', '图片尺寸不符合裁剪要求')
            return
          }
          // 计算出宽高比
          imgAspectRatio.value = divide(imgWidth.value, imgHeight.value)
          initCrop() //初始化裁剪容器及其图片
          loading.value = false
        }
        imageElement = _img
      } catch (error) {
        emits('on-error', error)
      }
    }
  }
})
</script>
<style lang="scss" scoped>
@use "./bem.scss" as *;
@include b(crop) {
  @include e(wrap) {
    display: flex;
    flex-direction: row;
    padding-bottom: 20px;
    img {
      user-select: none;
    }
  }
  @include e(title) {
    font-size: 12px;
    color: #9aa2aa;
    margin-bottom: 10px;
    user-select: none;
  }
  @include e(croparea) {
    padding-right: 20px;
  }
  @include e(preview) {
    // flex: 1;
  }
  @include e(cropbox) {
    width: 480px;
    height: 250px;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    background-color: #000;
    border-radius: 4px;
    overflow: hidden;
    img {
      width: 100%;
      height: 100%;
    }
  }
  @include e(previewbox) {
    border-radius: 4px;
    box-shadow: 0 1px 3px 0 #d0d5de;
    background-color: #999;
    overflow: hidden;
    img {
      position: absolute;
      left: 0;
      top: 0;
    }
  }
  @include e(imgbox) {
    position: relative;
  }
  @include e(select) {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    z-index: 1;
    background-color: rgba(0, 0, 0, 0.5);
    @include e(area) {
      position: absolute;
      top: 0;
      left: 0;
      z-index: 1;
      cursor: move;
      border-color: #fff;
      overflow: hidden;
      border-style: solid;
      box-sizing: border-box;
      img {
        position: absolute;
        pointer-events: none;
      }
    }
  }
}
.button-bar {
  display: flex;
  align-items: center;
  justify-content: center;
}
</style>
